1.4. Functions

函数将代码分解为可管理的部分并减少代码重复。函数可能采用零个或多个参数作为输入,并且它们返回特定类型的单个值。函数声明(declaration)或原型(prototype)指定函数的名称、返回类型及其参数列表(所有参数的数量和类型)。函数定义(definition)包括调用函数时要执行的代码。 C 中的所有函数都必须在调用之前声明。这可以通过声明函数原型或在调用函数之前完全定义该函数来完成:

// function definition format:
// ---------------------------
<return type> <function name> (<parameter list>)
{
    <function body>
}

// parameter list format:
// ---------------------
<type> <param1 name>, <type> <param2 name>, ...,  <type> <last param name>

这是一个函数定义示例。请注意,注释描述了函数的作用、每个参数的详细信息(其用途和应传递的内容)以及函数返回的内容:

/* This program computes the larger of two
 * values entered by the user.
 */
#include <stdio.h>

/* max: computes the larger of two integer values
 *   x: one integer value
 *   y: the other integer value
 *   returns: the larger of x and y
 */
int max(int x, int y) {
    int bigger;

    bigger = x;
    if (y > x) {
        bigger = y;
    }
    printf("  in max, before return x: %d y: %d\n", x, y);
    return bigger;
}

没有返回值的函数应指定 void 返回类型。以下是 void 函数的示例:

/* prints out the squares from start to stop
 *   start: the beginning of the range
 *   stop: the end of the range
 */
void print_table(int start, int stop) {
    int i;

    for (i = start; i <= stop; i++) {
        printf("%d\t", i*i);
    }
    printf("\n");
}

与任何支持函数或过程的编程语言一样,函数调用会调用函数,为特定调用传递特定的参数值。函数通过其名称来调用,并传递参数,每个相应的函数形参(parameter)都有一个实参(argument)。在 C 语言中,调用函数如下所示:

// 函数调用格式:
// ---------------------
function_name(<argument list>);


// 参数列表格式:
// ---------------------
<argument 1 expression>, <argument 2 expression>, ...,  <last argument expression>

C 函数的参数按值传递(passed by value):每个函数形参(parameter)都分配有调用者在函数调用中传递给它的相应实参(argument)的值。按值传递语义意味着对函数中参数值(形参)的任何更改(即在函数中为形参分配新值)对调用者来说 不可见

以下是对前面列出的maxprint_table函数的一些示例函数调用:

int val1, val2, result;

val1 = 6;
val2 = 10;

/* to call max, pass in two int values, and because max returns an
   int value, assign its return value to a local variable (result)
 */
result = max(val1, val2);     /* call max with argument values 6 and 10 */
printf("%d\n", result);       /* prints out 10 */

result = max(11, 3);          /* call max with argument values 11 and 3 */
printf("%d\n", result);       /* prints out 11 */

result = max(val1 * 2, val2); /* call max with argument values 12 and 10 */
printf("%d\n", result);       /* prints out 12 */

/* print_table does not return a value, but takes two arguments */
print_table(1, 20);           /* prints a table of values from 1 to 20 */
print_table(val1, val2);      /* prints a table of values from 6 to 10 */

这是完整程序的另一个示例,它显示了对max函数的稍微不同的实现的调用,该函数有一个附加语句来更改其参数的值(“x = y”):

/* max: computes the larger of two int values
 *   x: one value
 *   y: the other value
 *   returns: the larger of x and y
 */
int max(int x, int y) {
    int bigger;

    bigger = x;
    if (y > x) {
        bigger = y;
        // note: changing the parameter x's value here will not
        //       change the value of its corresponding argument
        x = y;
    }
    printf("  in max, before return x: %d y: %d\n", x, y);

    return bigger;
}

/* main: shows a call to max */
int main(void) {
    int a, b, res;

    printf("Enter two integer values: ");
    scanf("%d%d", &a, &b);

    res = max(a, b);
    printf("The larger value of %d and %d is %d\n", a, b, res);

    return 0;
}

以下输出显示了该程序的两次运行可能是什么样子。请注意两次运行中参数x的值(从max函数内部打印)的差异。具体来说,请注意,在第二次运行中更改参数x的值不会影响调用返回后作为参数传递给max的变量:

$ ./a.out
Enter two integer values: 11  7
  in max, before return x: 11 y: 7
The larger value of 11 and 7 is 11

$ ./a.out
Enter two integer values: 13  100
  in max, before return x: 100 y: 100
The larger value of 13 and 100 is 100

由于参数是通过值传递给函数的,因此更改其参数值之一的先前版本的max函数的行为与未更改参数值的原始版本的max行为相同。

1.4.1. The Stack

执行栈跟踪程序中活动函数的状态。每个函数调用都会创建一个新的栈帧stack frame,有时称为活动帧活动记录),其中包含其参数和局部变量值。栈顶的帧是活动帧;它代表当前正在执行的函数激活,并且只有其局部变量和参数在范围内。当调用函数时,会为其创建一个新的栈帧(在栈顶部 压栈),并在新帧中为其局部变量和参数分配空间。当函数返回时,其栈帧将从堆栈中删除(从堆栈顶部 弹出),将调用者的栈帧保留在栈顶部。

对于前面的示例程序,在max执行return语句之前的执​​行点,执行堆栈将类似于[图 1](https://diveintosystems.org/book/C1-C_intro/functions .html#FigFunctionSimple)。回想一下,由main传递给max的参数值是 按值传递 的,这意味着max的形参,xy被分配了它们相应来自main中的调用的实参参数ab的值。尽管max函数更改了x的值,但该更改不会影响maina的值。

A stack with two frames: main at the bottom, and max on top of it.  Main’s stack frame has three variables, a (11), b (7) and res (undefined at this point).  Max’s stack frame also has three variables, x (11), y (7), and bigger (11). 图 1. max 函数返回之前的执行堆栈内容

以下完整程序包含两个函数,并显示了从main函数调用它们的示例。在此程序中,我们在main函数上方声明maxprint_table的函数原型,以便main尽管首先定义,但仍可以访问它们。 main函数包含整个程序的高级步骤,首先定义它与程序的自上而下的设计相呼应。此示例包含描述程序中对函数和函数调用重要的部分的注释。您还可以下载并运行完整程序

/* This file shows examples of defining and calling C functions.
 * It also demonstrates using scanf().
 */

#include <stdio.h>

/* This is an example of a FUNCTION PROTOTYPE.  It declares just the type
 * information for a function (the function's name, return type, and parameter
 * list). A prototype is used when code in main wants to call the function
 * before its full definition appears in the file.
 */
int max(int n1, int n2);

/* A prototype for another function.  void is the return type of a function
 * that does not return a value
 */
void print_table(int start, int stop);

/* All C programs must have a main function.  This function defines what the
 * program does when it begins executing, and it's typically used to organize
 * the big-picture behavior of the program.
 */
int main(void) {
    int x, y, larger;

    printf("This program will operate over two int values.\n");

    printf("Enter the first value: ");
    scanf("%d", &x);

    printf("Enter the second value: ");
    scanf("%d", &y);

    larger = max(x, y);

    printf("The larger of %d and %d is %d\n", x, y, larger);

    print_table(x, larger);

    return 0;
}

/* This is an example of a FUNCTION DEFINITION.  It specifies not only the
 * function name and type, but it also fully defines the code of its body.
 * (Notice, and emulate, the complete function comment!)
 */
/* Computes the max of two integer values.
 *   n1: the first value
 *   n2: the other value
 *   returns: the larger of n1 and n2
 */
int max(int n1, int n2)  {
    int result;

    result = n1;

    if (n2 > n1) {
        result = n2;
    }

    return result;
}

/* prints out the squares from start to stop
 *   start: the beginning of the range
 *   stop: the end of the range
 */
void print_table(int start, int stop) {
    int i;

    for (i = start; i <= stop; i++) {
        printf("%d\t", i*i);
    }

    printf("\n");
}